Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use union types instead of join in binder #18538

Merged
merged 10 commits into from
Feb 5, 2025

Conversation

ilevkivskyi
Copy link
Member

@ilevkivskyi ilevkivskyi commented Jan 26, 2025

This would be more consistent with what we already do for ternary expressions. Note the change in match test results from match logic not handling well the situation when initial type is a union. A possible workaround would be to force "collapsing" union of tuples back into a tuple with union, but it is not easy and was planning to do some cleanup in the match handling as well (in particular it uses joins instead of unions in a way that will be inconsistent with new binder behavior). I want to put the switch from join to union for match statement in a separate PR.

Note I also simplify a bunch of special-casing around Any in the binder that existed mostly because join(Any, X) == Any.
Fixes #3724

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

@ilevkivskyi
Copy link
Member Author

OK, so 90% of the new errors are because join(Any, X) == Any but now it is Any | X. The remaining few simply expose existing mypy limitations that used to work accidentally, most notably

class Base:
    def foo(self, x: Any): ...
class C1(Base):
    def foo(self, x: int): ...
class C2(Base):
    def foo(self, x: str): ...

b: Base
x: str | int
if isinstance(x, int):
    b = C1()
else:
    b = C2()
b.foo(x)

This used to work when using joins, but not anymore, since binder doesn't support "lock step" assignments. Coming back to join(Any, X) == Any, this reminds me that we have a bunch of weird special-casing for Any in binder to work around this. Now we don't need this anymore. Even if some people are now used to this weird special casing, I propose to rip of this band-aid now.

This comment has been minimized.

@ilevkivskyi
Copy link
Member Author

OK, the fallout is much larger now, but IMO it looks very promising. I will slowly go though the mypy_primer next week. If anyone has time to check if there are things that are clearly new false positives/negatives please let me know, especially if you can find a simple repro.

@@ -631,11 +631,12 @@ class Child(Parent):
def bar(self) -> int:
if 1:
self = super(Child, self).something()
reveal_type(self) # N: Revealed type is "__main__.Child"
reveal_type(self) # N: Revealed type is "Any"
# TODO: we should probably make this unreachable similar to above.
Copy link
Collaborator

@A5rocks A5rocks Jan 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is #18386 ?

Copy link
Member Author

@ilevkivskyi ilevkivskyi Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bit more tricky than that. In that example:

x: int
if x is None: ...

the branch is obviously unreachable. But if I say:

x: Any
if x is None: ...

then technically it is reachable because x: Any = None is valid, but (highly) unlikely.

@cdce8p
Copy link
Collaborator

cdce8p commented Jan 27, 2025

Thanks for working on this @ilevkivskyi!

I did a full mypy run for Home Assistant with these changes. Unfortunately, out of the 53 new errors, only 2 seem to be helpful. The most common reason for false positives is that explicit assignments seem to get lost after an Any assignment. In the past that was one of the ways to work around untyped code. This is even more an issue with --warn-return-any. Below are some examples.

# mypy: warn-return-any, allow-empty-bodies

from typing import Any

def process_config(config):
    # Untyped function in a dependency
    ...

def f1(config: dict[str, Any]) -> dict[str, Any]:
    config = process_config(config)
    reveal_type(config)  # dict[str, Any] -> Any
    return config


# --
def f2(config: dict[str, Any]) -> int:
    res: int
    res = config["key"]
    reveal_type(res)  # int -> Any
    return res


# --
class A3:
    y: int = 2

def f3(x: A3) -> int:
    res: int
    res = getattr(x, "y")
    reveal_type(res)  # int -> Any
    return res


# --
def get_devices() -> list[dict[str, list[str] | str]]: ...

class A4:
    devices: list[dict[str, Any]] | None

    def update_devices(self) -> dict[str, str]:
        if self.devices is None:
            self.devices = get_devices()

        data: dict[str, str] = {}
        for device in self.devices:
            id_ = str(device["id"])
            name = device["name"]
            reveal_type(name)  # Any -> list[str] | str | Any
            data[id_] = name
        return data


# --
def expect_str(var: str) -> None: ...

def f5(x: Any) -> None:
    state: str | None = None
    state = x
    expect_str(state)  # arg-type error -> fine (false-positive)

def f6(x: Any) -> float:
    state: float
    state = x
    reveal_type(state)  # float -> Any
    return round(state)


# --
def f7(config: dict[str, Any] | None, x: Any) -> None:
    config = x
    config.copy()  # union-attr error -> fine (false-positive)

@ilevkivskyi
Copy link
Member Author

@cdce8p First of all it seems to me you may be using false-positive wrong in some examples. To be clear, false-positive means that the code is actually correct, but mypy erroneously complains (so it is like in medical tests: positive means bad).

That said, I actually think in all the examples you posted previous behavior was wrong, and the new behavior is correct. Moreover, I would say it is as correct as it can be. To show a bit more, consider this:

def untyped(): ...
x = 42
x = untyped()
reveal_type(x)

here the reveal type must be for all intents and purposes be Any, since the previous value is gone forever, this code is semantically equivalent to

def untyped(): ...
x0 = 42
x1 = untyped()
reveal_type(x1)

Whether that is convenient or not is different question. IMO previous behavior only gave you false sense of security. To this point, can you try to find an actual runtime error with your examples that would have been caught before, but not with this PR? (Unless I am missing something I think you can't). Also don't worry, types are not "completely lost" after Any assignment, e.g. this is still an error:

x: int
x = untyped()
x = "no way"  # E: Incompatible types in assignment (expression has type "str", variable has type "int")

Btw the example with class A4 IMO is an actual bug that mypy now catches, consider:

def get_devices() -> list[dict[str, list[str] | str]]:
    return [{"id": "1", "name": ["foo", "bar"]}]

which is valid for the type. Then if you do:

a4 = A4()
a4.devices = None
a4.update_devices()["1"].lower()

you will get AttributeError: 'list' object has no attribute 'lower'.

@cdce8p
Copy link
Collaborator

cdce8p commented Jan 27, 2025

First of all it seems to me you may be using false-positive wrong in some examples.

You're right. Those are errors which are no longer emitted but maybe still should, so false-negative. Unless I've mixed them up again 😅

Whether that is convenient or not is different question. IMO previous behavior only gave you false sense of security. To this point, can you try to find an actual runtime error with your examples that would have been caught before, but not with this PR? (Unless I am missing something I think you can't).

It's true, that the initial type assignment was more a hack than anything else. However, the alternative would be to use cast for all assignments which return Any which could be a lot. It might also be worth taking a look at pyright here. It also infers config["key"] as Any, however res will still be just int afterwards.

def f2(config: dict[str, Any]) -> int:
    res: int
    res = config["key"]
    reveal_type(res)  # int -> Any
    res.lower()  # attr-defined -> no error
    return res

Btw the example with class A4 IMO is an actual bug that mypy now catches, consider:

def get_devices() -> list[dict[str, list[str] | str]]:
    return [{"id": "1", "name": ["foo", "bar"]}]

Correct. However, in this example the value type list[str] was added to type an additional key besides id and name. A TypedDict return type would of course be the better option and resolve that immediately, but it's unfortunately much more common to see union types in reality. Especially from library authors not as comfortable with typing as we are.

@JukkaL
Copy link
Collaborator

JukkaL commented Jan 27, 2025

Can the change where assigning an Any value changes the type of an annotated variable to Any be split off to a separate PR?

My longer-term plan/hope is to allow each assignment to infer a separate type for a variable (see #18516), which would also enable inferring Any types in many cases, but critically if a variable has an explicit annotation, this would be disabled, so the impact to existing use cases should be smaller. So my change might be an alternative way of achieving a similar result, but it will take some more effort before it's ready for merging.

This example illustrates how my change would (likely) work:

def a() -> Any: ...

def f1() -> None:
    x: int = 1
    x = a()  
    reveal_type(x)  # "int", since 'x' has an explicit type annotation and won't be renamed

def f2() -> None:
    x = 1
    # ... do something with x
    x = a()
    reveal_type(x)  # "Any", since the two definitions are treated as two separate variables

@ilevkivskyi
Copy link
Member Author

@JukkaL I would rather not do this. For three reasons:

  • I don't see a single good reason to special-case Any. As far I remember the only reason we introduced this special-casing in the first place is because join(X, Any) == Any so assigning Any in at least one of the branches made the whole resulting type Any thus creating many false negatives.
  • This special casing is already complicated, currently we don't assign assign Any, unless the initial type is a union with Any, but we still assign it if the last know type is None and the declared type optional, but in this case we don't assign the Any but the declared type where None is replaced with Any. Adding one more rule in addition to these two will create double nested exceptions to exceptions.
  • Current logic creates actual false positives, as you can see from dozens of Unused "type: ignore" comment in mypy_primer.

@cdce8p You know I don't share many of the pyright's design choices, and this is no exception. Btw I am wondering maybe this one was actually influenced by current mypy's behavior. Anyway coming back to your example, IMO the only form that may be special-cased (and actually currently is, even with this PR) is x: int = untyped() (in the initial assignment). Moreover, this is not specific to Any, consider this:

class B: ...
class C(B): ...
def test() -> list[B]:
    x: B = C()
    res = [x]
    return res  # If we would narrow the type of x, this would be an error

This is an even older discussion however, see #2008

@cdce8p
Copy link
Collaborator

cdce8p commented Jan 27, 2025

@cdce8p You know I don't share many of the pyright's design choices, and this is no exception.

I know. There are quite a few I don't like as well.

Btw I am wondering maybe this one was actually influenced by current mypy's behavior.

Don't think so. My guess would be that pyright whats to provide the best experience from a language server perspective. I.e. if the user declares a type to be x: str, then just accept that it will always be str. That way pylance can create suggestions even after an Any assignment. Going even further, if the underlying type of Any isn't str that would be an error in the assignment which should be reported there, not at the use / return.

To extend on one of my initial examples:

def f2(config: dict[str, Any]) -> int:
    res: int
    res = config["key"]
    reveal_type(res)  # int -> Any
    return res.upper()  # <-- this will fail

Say, we can't adjust the type of config for a moment, to make mypy notice the error with this PR, I'd have to add either add assert isinstance(res, int) or res = cast(int, config["key"]). At some point the type of config in the dependency will be changed to a TypedDict, say {key: int, ...}. This just leaves unnecessary asserts / cast everywhere.

If the type is instead {key: list[int], ...}, the error should and would be emitted on the assignment res = config["key"], not res.upper().

In the end it's just my experience, but I still think that changing the type to Any, although technically correct, would be a worse user experience for many, myself included.

@ilevkivskyi
Copy link
Member Author

Actually after some more thinking and reading old PRs it seems to me we can try the idea by @JukkaL (undo the last commit + don't assign Any if there is an explicit annotation) it will only add couple lines to this PR. But I think we still need to simplify the current logic. For example:

  • If there is a local variable with no explicit annotation always assign Any
  • Otherwise only assign Any if the initial type is a union.

The second rule will roughly match the current special-casing, but is much simpler to remember.

This comment has been minimized.

This comment has been minimized.

@ilevkivskyi
Copy link
Member Author

ilevkivskyi commented Jan 29, 2025

For those who are following here, after a discussion with @JukkaL we came up with some compromise behavior that replaces all previous ad-hoc special-casing of Any, so the new rules when Any value is assigned to a variable are:

  • If the variable has no explicit annotation, narrow the type to Any
  • If the variable has an explicit X | Any annotation, narrow the type to Any
  • If the variable has an explicit X | None annotation, narrow the type to X | Any
  • In all other cases, reset the variable type to its declared type.

The mypy_primer output is still large. The good sign is that we still have 54 Unused "type: ingnore" errors. I will try to spot-check most of the output today-tomorrow. FWIW I am most concerned about pandas, so will first go through all the errors there.

@cdce8p
Copy link
Collaborator

cdce8p commented Jan 31, 2025

Thanks for the update! I had a chance to check the output again and it looks much better than before. For Home Assistant we're down to 18 errors now of which 12 are improvements.

The good sign is that we still have 54 Unused "type: ignore" errors.

It isn't that simple unfortunately. 2 of 3 Unused "type: ignore" errors for Home Assistant are because mypy can't detect true errors anymore.

The main advantage I see with the current changes is that a variable previously without annotation or typed as Any can now be narrowed with an assignment. Some examples of improvements:

# mypy: warn-return-any, allow-empty-bodies
from typing import Any
def untyped(): ...


def great1(var: Any) -> list[Any]:
    if not isinstance(var, list):
        var = [var]
    reveal_type(var)  # Any -> list[Any]
    return var  # no-any-return (false-positive) -> fine


def great2(val: Any, val2: float) -> None:
    response: int = 0
    val = int(val)
    reveal_type(val)  # Any -> int
    response = min(0, val + val2)  # fine -> assignment error (true-positive)
                                   # response should be typed as 'float'


def great3(max_volume: int | None) -> None:
    volume = untyped()
    volume = int(volume or 0)
    min(volume, max_volume)  # fine -> type-var error (true-positive)
                             # should be 'max_volume or 0'


def great4(other_val: float | None) -> float:
    value = untyped()
    value = other_val
    return value  # no-any-return -> return-type (true-positive)
                  # should check for 'None' first


class G5:
    def __init__(self, name: str) -> None: ...

def great5(val: Any, val2: str | None) -> None:
    if int():
        name = val
        G5(name)
    else:
        name = val2
        G5(name)  # fine -> arg-type error (true-positive)
                  # should check for 'None' first

As mentioned earlier, the case that's still causing issues is a variable with an explicit annotation gets assigned Any and thus mypy can't detect follow up error anymore. I don't think replacing str | None with str | Any is the right call here. The assumption in code is usually that x will also be of type str | None. It's just Any for some other reason, e.g. because the library is untyped.

def b1(x: Any) -> str:
    state: str | None = None
    state = x
    reveal_type(state)  # str | None -> str | Any
    return state  # return-value error -> fine (false-negative)
                  # should check for 'None' first

@JukkaL
Copy link
Collaborator

JukkaL commented Jan 31, 2025

I don't think replacing str | None with str | Any is the right call here.

This is pretty ad hoc, but my understanding is that this preserves existing behavior. I think it's reasonable to consider changing this, but I'd prefer if we'd do it independently of the other changes so that it would be easier to estimate the impact. If this would cause many (apparent) false positives, it might make sense to provide a config flag for a smoother upgrade path.

@ilevkivskyi
Copy link
Member Author

@cdce8p Yeah, as Jukka said, it was mypy's behavior since long time ago. But it was relatively inconsistent so far, we decided to simplify the rules on when it is applied as much as reasonable. The current recommendation for people who want to preserve optional safety in presence of Any is to use Any | None as an annotation when needed (don't simplify it to just Any). I vaguely remember we shared some communication on this when disabling implicit_optional by default. I think we may need to remind this in the release notes for the coming release.

@JukkaL while looking at some of the errors in the primer, I think I see some of the rule to not narrow type if last known type is Any. I think it may be useful for an explicit Any annotation, so that it would mirror the rule of not assigning Any if there is an explicit non-Any annotation.

An important point: all function arguments are treated as explicitly annotated vars (mostly for self and/or cls), so this rule would preserve the current behavior for unannotated functions. Otherwise I am afraid this PR will make fallout from --check-untyped-defs worse.

I am going to push a commit soon trying this as an experiment, and check mypy_primer again.

@cdce8p
Copy link
Collaborator

cdce8p commented Jan 31, 2025

I vaguely remember we shared some communication on this when disabling implicit_optional by default. I think we may need to remind this in the release notes for the coming release.

According to the docs it was disabled by default in 0.980.
https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-implicit-optional

The current recommendation for people who want to preserve optional safety in presence of Any is to use Any | None as an annotation when needed (don't simplify it to just Any).

To come back to the example from above:

def b1(x: Any) -> str:
    state: str | None = None
    state = x
    reveal_type(state)  # str | None -> str | Any
                  # should check for 'None' first
    return state  # return-value error -> fine (false-negative)

x: Any can very well be a untyped function call as well, so I have no option to change that. I'd need to adjust the state type here to str | Any | None. Doesn't that defeat the purpose of gradual typing though? Consider the following user story for a moment with the function b1 and the assignment state = untyped() instead:

  • Currently, mypy "ignores" the Any assignment and just continues with the initial assigned type.
  • Even though the state = untyped() assignment isn't checked, as long as the true return type is str | None it will work just fine.
  • Any followup errors based on the initial assumption are also detected correctly.

Changing it to str | Any | None now:

  • The user might get a "no-any-return" warning
  • If annotations are added to untyped, the improvements won't automatically be detected and used by mypy.
  • Instead, it's necessary to remove the explicit Any annotation first. Difficult to do as you often aren't aware at which point a specific annotation in a dependency is added.
  • An alternative, just adding type: ignore[no-any-return], would ignore the error, but it doesn't help detect other errors which now get masked with Any.
  • If the added annotation for untyped is incompatible with the initial assumption, e.g. int | None is returned, with the current change, mypy would also just ignore it as it's masked with Any as well. Without this change, the error would be emitted on the assignment, like in every other case as well.

The point I'm trying to make here is that I don't see a case where it's arguably an improvement to "overwrite" the explicit annotation. Mypy should trust the user here IMO.

@ilevkivskyi
Copy link
Member Author

@cdce8p

I don't see a case where it's arguably an improvement to "overwrite" the explicit annotation

For example this:

def fallback(): return 42  # untyped

def use_fallback(x: int | None) -> int:
    if x is None:
        x = fallback()
    return x

without special casing this will result in a false-positive. And a false positive is usually more annoying than a false negative.

@ilevkivskyi
Copy link
Member Author

@cdce8p

I'd need to adjust the state type here to str | Any | None.

No, you need to change annotation on x to Any | None, so that you will have

def b1(x: Any | None) -> str:
    state: str | None = None
    state = x
    reveal_type(state)  # Any | None
    return state

This comment has been minimized.

Copy link
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

arviz (https://github.com/arviz-devs/arviz)
+ arviz/data/inference_data.py:442: error: Argument 2 to "InferenceData" has incompatible type "**dict[Any, Dataset]"; expected "bool"  [arg-type]

optuna (https://github.com/optuna/optuna)
+ tests/trial_tests/test_frozen.py:108: error: Unused "type: ignore" comment  [unused-ignore]

cwltool (https://github.com/common-workflow-language/cwltool)
+ cwltool/pack.py: note: In function "find_run":
+ cwltool/pack.py:35:22: error: Argument 1 to "find_run" has incompatible type "str | int | float | MutableSequence[CWLOutputType] | MutableMapping[str, CWLOutputType] | Any | None"; expected "CWLObjectType | int | float | str | CommentedMap | CommentedSeq | None"  [arg-type]
+ cwltool/main.py: note: In function "load_job_order":
+ cwltool/main.py:377:41: error: Unsupported right operand type for in ("Any | int | float | str | CommentedMap | CommentedSeq")  [operator]
+ cwltool/main.py:379:49: error: Argument 1 to "resolve_overrides" has incompatible type "Any | int | float | str | CommentedMap | CommentedSeq"; expected "CommentedMap | CommentedSeq | str | None"  [arg-type]
+ cwltool/main.py:380:9: error: Item "int" of "Any | int | float | str | CommentedMap | CommentedSeq" has no attribute "__delitem__"  [union-attr]
+ cwltool/main.py:380:9: error: Item "float" of "Any | int | float | str | CommentedMap | CommentedSeq" has no attribute "__delitem__"  [union-attr]
+ cwltool/main.py:380:9: error: Item "str" of "Any | int | float | str | CommentedMap | CommentedSeq" has no attribute "__delitem__"  [union-attr]

pydantic (https://github.com/pydantic/pydantic)
+ pydantic/v1/main.py:901: error: Set comprehension has incompatible type Set[int | str]; expected Set[str | None]  [misc]
+ pydantic/v1/main.py:901: note: Left operand is of type "set[str] | dict_keys[str, Any]"

check-jsonschema (https://github.com/python-jsonschema/check-jsonschema)
+ src/check_jsonschema/schema_loader/main.py:190: error: Unexpected keyword argument "registry" for "Validator"  [call-arg]
+ note: "Validator" defined here
+ src/check_jsonschema/schema_loader/main.py:195: error: Redundant cast to "Validator"  [redundant-cast]

prefect (https://github.com/PrefectHQ/prefect)
- src/prefect/client/orchestration/__init__.py:1170: error: "str" has no attribute "major"  [attr-defined]
- src/prefect/client/orchestration/__init__.py:1511: error: "str" has no attribute "major"  [attr-defined]
- src/prefect/server/models/task_runs.py:138: error: Item "None" of "Optional[TaskRun]" has no attribute "created"  [union-attr]
- src/prefect/server/models/task_runs.py:141: error: Item "None" of "Optional[TaskRun]" has no attribute "id"  [union-attr]
- src/prefect/server/models/task_runs.py:147: error: Incompatible return value type (got "Optional[TaskRun]", expected "TaskRun")  [return-value]
- src/prefect/locking/filesystem.py:64: error: Unsupported target for indexed assignment ("Optional[_LockInfo]")  [index]
- src/prefect/locking/filesystem.py:65: error: Item "None" of "Optional[_LockInfo]" has no attribute "get"  [union-attr]
- src/prefect/locking/filesystem.py:66: error: Unsupported target for indexed assignment ("Optional[_LockInfo]")  [index]
- src/prefect/locking/filesystem.py:69: error: Incompatible types in assignment (expression has type "Optional[_LockInfo]", target has type "_LockInfo")  [assignment]
- src/prefect/locking/filesystem.py:86: error: Unsupported target for indexed assignment ("Optional[_LockInfo]")  [index]
- src/prefect/locking/filesystem.py:87: error: Item "None" of "Optional[_LockInfo]" has no attribute "get"  [union-attr]
- src/prefect/locking/filesystem.py:88: error: Unsupported target for indexed assignment ("Optional[_LockInfo]")  [index]
- src/prefect/locking/filesystem.py:91: error: Incompatible types in assignment (expression has type "Optional[_LockInfo]", target has type "_LockInfo")  [assignment]
- src/prefect/cli/config.py:70: error: "str" has no attribute "name"  [attr-defined]
- src/prefect/infrastructure/provisioners/container_instance.py:345: error: Item "None" of "Union[str, dict[str, Any], None]" has no attribute "__iter__" (not iterable)  [union-attr]
- src/prefect/infrastructure/provisioners/container_instance.py:375: error: Value of type "Union[str, dict[str, Any], None]" is not indexable  [index]
- src/prefect/infrastructure/provisioners/container_instance.py:375: error: Invalid index type "str" for "str"; expected type "Union[SupportsIndex, slice[Any, Any, Any]]"  [index]
- src/prefect/cli/deploy.py:677: error: Argument "actions" to "_generate_default_pull_action" has incompatible type "dict[str, Any]"; expected "list[dict[str, Any]]"  [arg-type]
+ src/prefect/cli/deploy.py:677: error: Argument "actions" to "_generate_default_pull_action" has incompatible type "Union[dict[str, list[dict[str, Any]]], dict[str, Any]]"; expected "list[dict[str, Any]]"  [arg-type]

altair (https://github.com/vega/altair)
+ altair/vegalite/v5/schema/channels.py:245: error: Unused "type: ignore" comment  [unused-ignore]

manticore (https://github.com/trailofbits/manticore)
- manticore/core/workspace.py:220: error: Argument 1 to "exists" has incompatible type "str | None"; expected "int | str | bytes | PathLike[str] | PathLike[bytes]"  [arg-type]
- manticore/core/workspace.py:221: error: Argument 1 to "isdir" has incompatible type "str | None"; expected "int | str | bytes | PathLike[str] | PathLike[bytes]"  [arg-type]
- manticore/core/workspace.py:223: error: Argument 1 to "mkdir" has incompatible type "str | None"; expected "str | bytes | PathLike[str] | PathLike[bytes]"  [arg-type]

SinbadCogs (https://github.com/mikeshardmind/SinbadCogs)
+ rolemanagement/core.py:420: error: Item "str" of "Union[Any, str]" has no attribute "id"  [union-attr]
+ rolemanagement/core.py:492: error: Item "str" of "Union[Any, str]" has no attribute "id"  [union-attr]

pandas (https://github.com/pandas-dev/pandas)
+ pandas/core/util/hashing.py:347: error: Incompatible return value type (got "Any | ndarray[tuple[int, ...], dtype[signedinteger[Any]]]", expected "ndarray[tuple[int, ...], dtype[unsignedinteger[_64Bit]]]")  [return-value]
+ pandas/core/nanops.py:641: error: Incompatible return value type (got "ndarray[Any, Any] | float | NaTType", expected "float")  [return-value]
+ pandas/_testing/_io.py:84: error: Unused "type: ignore" comment  [unused-ignore]
+ pandas/_testing/_io.py:85: error: Unused "type: ignore" comment  [unused-ignore]
+ pandas/_testing/_io.py:141: error: No overload variant of "ZipFile" matches argument types "Any", "str"  [call-overload]
+ pandas/_testing/_io.py:141: note: Possible overload variants:
+ pandas/_testing/_io.py:141: note:     def __init__(self, file: str | PathLike[str] | IO[bytes], mode: Literal['r', 'w', 'x', 'a'] = ..., compression: int = ..., allowZip64: bool = ..., compresslevel: int | None = ..., *, strict_timestamps: bool = ..., metadata_encoding: str | None = ...) -> ZipFile
+ pandas/_testing/_io.py:141: note:     def __init__(self, file: str | PathLike[str] | _ZipReadable, mode: Literal['r'] = ..., compression: int = ..., allowZip64: bool = ..., compresslevel: int | None = ..., *, strict_timestamps: bool = ..., metadata_encoding: str | None = ...) -> ZipFile
+ pandas/_testing/_io.py:141: note:     def __init__(self, file: str | PathLike[str] | _ZipWritable, mode: Literal['w', 'x'] = ..., compression: int = ..., allowZip64: bool = ..., compresslevel: int | None = ..., *, strict_timestamps: bool = ..., metadata_encoding: None = ...) -> ZipFile
+ pandas/_testing/_io.py:141: note:     def __init__(self, file: str | PathLike[str] | _ZipReadableTellable, mode: Literal['a'] = ..., compression: int = ..., allowZip64: bool = ..., compresslevel: int | None = ..., *, strict_timestamps: bool = ..., metadata_encoding: None = ...) -> ZipFile
+ pandas/_testing/_io.py:141: error: No overload variant of "BZ2File" matches argument types "Any", "str"  [call-overload]
+ pandas/_testing/_io.py:141: note:     def __init__(self, filename: _WritableFileobj, mode: Literal['w', 'wb', 'x', 'xb', 'a', 'ab'], *, compresslevel: int = ...) -> BZ2File
+ pandas/_testing/_io.py:141: note:     def __init__(self, filename: _ReadableFileobj, mode: Literal['', 'r', 'rb'] = ..., *, compresslevel: int = ...) -> BZ2File
+ pandas/_testing/_io.py:141: note:     def __init__(self, filename: str | bytes | PathLike[str] | PathLike[bytes], mode: Literal['', 'r', 'rb'] | Literal['w', 'wb', 'x', 'xb', 'a', 'ab'] = ..., *, compresslevel: int = ...) -> BZ2File
+ pandas/_testing/_io.py:141: error: Argument "mode" to "TarFile" has incompatible type "str"; expected "Literal['r', 'a', 'w', 'x']"  [arg-type]
+ pandas/core/computation/expr.py:679: error: Unused "type: ignore" comment  [unused-ignore]
+ pandas/core/computation/expr.py:679: error: Item "Attribute" of "Attribute | Name" has no attribute "id"  [union-attr]
+ pandas/core/computation/expr.py:679: note: Error code "union-attr" not covered by "type: ignore" comment
+ pandas/core/computation/expr.py:701: error: Unused "type: ignore" comment  [unused-ignore]
+ pandas/core/computation/expr.py:701: error: Item "Attribute" of "Attribute | Name" has no attribute "id"  [union-attr]
+ pandas/core/computation/expr.py:701: note: Error code "union-attr" not covered by "type: ignore" comment
+ pandas/core/common.py:295: error: Incompatible return value type (got "ndarray[Any, Any] | Iterable[Any]", expected "ndarray[Any, Any]")  [return-value]
+ pandas/core/algorithms.py:905: error: Incompatible types in assignment (expression has type "ndarray[Any, Any] | DatetimeArray | TimedeltaArray | PeriodArray | Any", variable has type "ndarray[tuple[int, ...], dtype[Any]]")  [assignment]
+ pandas/core/arrays/masked.py:706: error: Unused "type: ignore" comment  [unused-ignore]
+ pandas/io/pytables.py:3352: error: Incompatible return value type (got "list[Any]", expected "tuple[int, ...] | None")  [return-value]
+ pandas/io/parsers/c_parser_wrapper.py:123: error: Cannot determine type of "names"  [has-type]
+ pandas/core/series.py:2833: error: Argument 2 to "diff" has incompatible type "float | int | integer[Any]"; expected "int"  [arg-type]
+ pandas/core/apply.py:1911: error: "partial[Any]" has no attribute "__name__"; maybe "__new__"?  [attr-defined]
+ pandas/core/window/rolling.py:668: error: Unused "type: ignore" comment  [unused-ignore]
+ pandas/core/internals/managers.py:972: error: Incompatible types in assignment (expression has type "signedinteger[_32Bit | _64Bit]", variable has type "int")  [assignment]
+ pandas/core/indexes/datetimelike.py:602: error: "Index" has no attribute "_with_freq"; maybe "_with_infer"?  [attr-defined]
+ pandas/core/indexes/base.py:6078: error: "Index" has no attribute "_with_freq"; maybe "_with_infer"?  [attr-defined]
+ pandas/core/groupby/groupby.py:2881: error: Unused "type: ignore" comment  [unused-ignore]
+ pandas/core/groupby/groupby.py:5562: error: Argument "obj" to "SeriesGroupBy" has incompatible type "Series | DataFrame"; expected "Series"  [arg-type]
+ pandas/core/groupby/groupby.py:5562: error: Argument "obj" to "DataFrameGroupBy" has incompatible type "Series | DataFrame"; expected "DataFrame"  [arg-type]
+ pandas/core/interchange/column.py:338: error: Unused "type: ignore" comment  [unused-ignore]
+ pandas/io/sas/sas7bdat.py:298: error: Unused "type: ignore" comment  [unused-ignore]
+ pandas/io/formats/style.py:1790: error: "Never" has no attribute "shape"  [attr-defined]
+ pandas/io/formats/style.py:1793: error: "Never" has no attribute "shape"  [attr-defined]

sphinx (https://github.com/sphinx-doc/sphinx)
- sphinx/transforms/i18n.py:502:17: error: "Never" has no attribute "apply_translated_message"  [attr-defined]
+ sphinx/cmd/quickstart.py: note: In function "main":
+ sphinx/cmd/quickstart.py:803:21: error: Item "object" of "object | Any" has no attribute "__iter__" (not iterable)  [union-attr]

dd-trace-py (https://github.com/DataDog/dd-trace-py)
+ ddtrace/settings/_config.py:895: error: Incompatible types in assignment (expression has type "str", target has type "None")  [assignment]
+ ddtrace/internal/remoteconfig/client.py:520: error: Set comprehension has incompatible type Set[ConfigMetadata]; expected Set[str]  [misc]

artigraph (https://github.com/artigraph/artigraph)
- tests/arti/graphs/test_graph.py:30: error: "int" has no attribute "storage"  [attr-defined]
- tests/arti/graphs/test_graph.py:31: error: "Storage[Any]" has no attribute "id"  [attr-defined]
- tests/arti/graphs/test_graph.py:46: error: "int" has no attribute "storage"  [attr-defined]
- tests/arti/graphs/test_graph.py:56: error: Argument "a2" to "P2" has incompatible type "P1"; expected "A2"  [arg-type]

AutoSplit (https://github.com/Toufool/AutoSplit)
- src/user_profile.py:146:47: error: Argument 3 to "set_hotkey" has incompatible type "object"; expected "str"  [arg-type]

streamlit (https://github.com/streamlit/streamlit)
- lib/tests/streamlit/runtime/credentials_test.py: note: In member "test_Credentials_activate" of class "CredentialsClassTest":
- lib/tests/streamlit/runtime/credentials_test.py:276:30: error: "None" has no attribute "email"  [attr-defined]
- lib/tests/streamlit/runtime/credentials_test.py:277:30: error: "None" has no attribute "is_valid"  [attr-defined]
- lib/tests/streamlit/runtime/credentials_test.py: note: At top level:
- lib/tests/streamlit/runtime/scriptrunner/script_runner_test.py: note: In member "setUp" of class "ScriptRunnerTest":
- lib/tests/streamlit/runtime/scriptrunner/script_runner_test.py:94:9: error: Cannot assign to a method  [method-assign]
- lib/tests/streamlit/runtime/fragment_test.py: note: In member "test_wrapped_fragment_not_saved_in_FragmentStorage" of class "FragmentTest":
- lib/tests/streamlit/runtime/fragment_test.py:175:9: error: Cannot assign to a method  [method-assign]

pylint (https://github.com/pycqa/pylint)
+ pylint/checkers/deprecated.py:155: error: Argument 2 to "check_deprecated_class" of "DeprecatedMixin" has incompatible type "str | None"; expected "str"  [arg-type]

xarray (https://github.com/pydata/xarray)
+ xarray/core/indexing.py: note: In function "_decompose_outer_indexer":
+ xarray/core/indexing.py:1259: error: Argument 1 to "_decompose_slice" has incompatible type "number[Any, int | float | complex]"; expected "slice[Any, Any, Any]"  [arg-type]
+ xarray/core/indexing.py:1297: error: Argument 1 to "_decompose_slice" has incompatible type "number[Any, int | float | complex]"; expected "slice[Any, Any, Any]"  [arg-type]
+ xarray/core/computation.py: note: In function "apply_variable_ufunc":
+ xarray/core/computation.py:848: error: "Never" has no attribute "ndim"  [attr-defined]
+ xarray/core/computation.py:851: error: "Never" has no attribute "ndim"  [attr-defined]

black (https://github.com/psf/black)
+ src/blib2to3/pgen2/parse.py:103:23: error: Argument 1 to "_addtoken" of "Parser" has incompatible type "*list[object]"; expected "int"  [arg-type]
+ src/blib2to3/pgen2/parse.py:103:23: error: Argument 1 to "_addtoken" of "Parser" has incompatible type "*list[object]"; expected "str"  [arg-type]
+ src/blib2to3/pgen2/parse.py:103:23: error: Argument 1 to "_addtoken" of "Parser" has incompatible type "*list[object]"; expected "tuple[str, tuple[int, int]]"  [arg-type]
+ src/blib2to3/pgen2/parse.py:103:23: error: Argument 1 to "addtoken" of "Parser" has incompatible type "*list[object]"; expected "int"  [arg-type]
+ src/blib2to3/pgen2/parse.py:103:23: error: Argument 1 to "addtoken" of "Parser" has incompatible type "*list[object]"; expected "str"  [arg-type]
+ src/blib2to3/pgen2/parse.py:103:23: error: Argument 1 to "addtoken" of "Parser" has incompatible type "*list[object]"; expected "tuple[str, tuple[int, int]]"  [arg-type]
+ src/black/cache.py:45:5: error: Returning Any from function declared to return "Path"  [no-any-return]

spark (https://github.com/apache/spark)
+ python/pyspark/sql/udf.py:683: error: "UserDefinedFunctionLike" has no attribute "_unwrapped"  [attr-defined]
+ python/pyspark/sql/functions/builtin.py:2615: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/functions/builtin.py:2669: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/functions/builtin.py:3056: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/functions/builtin.py:7802: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/functions/builtin.py:7858: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/functions/builtin.py:8889: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/functions/builtin.py:8944: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/functions/builtin.py:8999: error: Unused "type: ignore" comment  [unused-ignore]
- python/pyspark/pandas/generic.py:2681: error: PandasDataFrameLike? has no attribute "iloc"  [attr-defined]
+ python/pyspark/pandas/groupby.py:3701: error: Item "tuple[Any, ...]" of "Series[Any] | tuple[Any, ...]" has no attribute "rename"  [union-attr]
+ python/pyspark/pandas/groupby.py:3701: error: Argument 3 to "align_diff_frames" has incompatible type "Series[Any] | Any"; expected "DataFrame[Any]"  [arg-type]
+ python/pyspark/pandas/groupby.py:3713: error: Item "tuple[Any, ...]" of "Series[Any] | tuple[Any, ...]" has no attribute "name"  [union-attr]
+ python/pyspark/pandas/indexes/multi.py:1180: error: Argument 1 to "_index_fields_for_union_like" of "Index" has incompatible type "DataFrame[Any] | Series[Any] | Index | list[Any]"; expected "Index"  [arg-type]
+ python/pyspark/sql/connect/functions/builtin.py:578: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/connect/functions/builtin.py:597: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/connect/functions/builtin.py:609: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/connect/functions/builtin.py:691: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/connect/functions/builtin.py:826: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/connect/functions/builtin.py:1571: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/connect/functions/builtin.py:1585: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/sql/connect/functions/builtin.py:1599: error: Unused "type: ignore" comment  [unused-ignore]

mongo-python-driver (https://github.com/mongodb/mongo-python-driver)
+ pymongo/synchronous/cursor.py:1004: error: Invalid index type "int | Any | Pattern[Any]" for "list[Any]"; expected type "SupportsIndex"  [index]
+ pymongo/asynchronous/cursor.py:1006: error: Invalid index type "int | Any | Pattern[Any]" for "list[Any]"; expected type "SupportsIndex"  [index]

speedrun.com_global_scoreboard_webapp (https://github.com/Avasam/speedrun.com_global_scoreboard_webapp)
- backend/api/tournament_scheduler_api.py:327: error: No overload variant of "__getitem__" of "list" matches argument type "str"  [call-overload]
- backend/api/tournament_scheduler_api.py:327: note: Possible overload variants:
- backend/api/tournament_scheduler_api.py:327: note:     def __getitem__(self, SupportsIndex, /) -> Any
- backend/api/tournament_scheduler_api.py:327: note:     def __getitem__(self, slice[Any, Any, Any], /) -> list[Any]
- backend/api/tournament_scheduler_api.py:331: error: No overload variant of "__getitem__" of "list" matches argument type "str"  [call-overload]
- backend/api/tournament_scheduler_api.py:331: note: Possible overload variants:
- backend/api/tournament_scheduler_api.py:331: note:     def __getitem__(self, SupportsIndex, /) -> Any
- backend/api/tournament_scheduler_api.py:331: note:     def __getitem__(self, slice[Any, Any, Any], /) -> list[Any]
- backend/api/tournament_scheduler_api.py:335: error: No overload variant of "__getitem__" of "list" matches argument type "str"  [call-overload]
- backend/api/tournament_scheduler_api.py:335: note: Possible overload variants:
- backend/api/tournament_scheduler_api.py:335: note:     def __getitem__(self, SupportsIndex, /) -> Any
- backend/api/tournament_scheduler_api.py:335: note:     def __getitem__(self, slice[Any, Any, Any], /) -> list[Any]

pytest (https://github.com/pytest-dev/pytest)
+ src/_pytest/config/__init__.py:342: error: Returning Any from function declared to return "Config"  [no-any-return]
+ src/_pytest/fixtures.py:1107: error: Returning Any from function declared to return "FixtureValue"  [no-any-return]

sympy (https://github.com/sympy/sympy)
+ sympy/core/evalf.py:629: error: Argument 1 to "scaled_zero" has incompatible type "tuple[int, int, int, int] | tuple[list[int], int, int, int] | None"; expected "tuple[list[int], int, int, int]"  [arg-type]
+ sympy/core/evalf.py:631: error: Argument 1 to "scaled_zero" has incompatible type "tuple[int, int, int, int] | tuple[list[int], int, int, int] | None"; expected "tuple[list[int], int, int, int]"  [arg-type]
+ sympy/core/evalf.py:917: error: Argument 1 to "evalf" has incompatible type "Expr | None"; expected "Expr"  [arg-type]
+ sympy/integrals/manualintegrate.py:1568: error: Unused "type: ignore" comment  [unused-ignore]
+ sympy/tensor/array/expressions/from_array_to_matrix.py:787: error: Unused "type: ignore" comment  [unused-ignore]

openlibrary (https://github.com/internetarchive/openlibrary)
+ openlibrary/core/imports.py: note: At top level:
+ openlibrary/core/imports.py:234: error: Unused "type: ignore" comment  [unused-ignore]
+ openlibrary/core/imports.py:235: error: Unused "type: ignore" comment  [unused-ignore]

scrapy (https://github.com/scrapy/scrapy)
+ scrapy/core/downloader/handlers/http11.py:393: error: Argument "proxyConf" has incompatible type "tuple[str | None, int, bytes | None]"; expected "tuple[str, int, bytes | None]"  [arg-type]

materialize (https://github.com/MaterializeInc/materialize)
- ci/mkpipeline.py:691: error: Unsupported right operand type for in ("PipelineStep | None")  [operator]
- ci/mkpipeline.py:692: error: Unsupported target for indexed assignment ("PipelineStep | None")  [index]
- ci/mkpipeline.py:694: error: Item "PipelineStep" of "PipelineStep | None" has no attribute "get"  [union-attr]
- ci/mkpipeline.py:694: error: Item "None" of "PipelineStep | None" has no attribute "get"  [union-attr]

paroxython (https://github.com/laowantong/paroxython)
+ paroxython/derived_labels_db.py:189: error: Invalid index type "str | Any" for "dict[LabelName, dict[Span, Any]]"; expected type "LabelName"  [index]

vision (https://github.com/pytorch/vision)
+ torchvision/datasets/video_utils.py:389: error: Unused "type: ignore" comment  [unused-ignore]

sockeye (https://github.com/awslabs/sockeye)
+ sockeye/data_io.py:771: error: Value of type "Iterable[Any]" is not indexable  [index]
+ sockeye/data_io.py:772: error: Value of type "Iterable[Any]" is not indexable  [index]

mkdocs (https://github.com/mkdocs/mkdocs)
+ mkdocs/utils/meta.py:71: error: Unused "type: ignore" comment  [unused-ignore]

pyppeteer (https://github.com/pyppeteer/pyppeteer)
+ pyppeteer/page.py:836: error: Unused "type: ignore" comment  [unused-ignore]

trio (https://github.com/python-trio/trio)
+ src/trio/_core/_run.py:2754: error: Argument 1 to "run" of "Context" has incompatible type "Optional[Callable[[Any], object]]"; expected "Callable[[Union[Outcome[Any], BaseException, None]], object]"  [arg-type]
+ src/trio/_tests/test_util.py:246: error: Unused "type: ignore" comment  [unused-ignore]
+ src/trio/_tests/test_util.py:262: error: Unused "type: ignore" comment  [unused-ignore]
+ src/trio/_tests/test_util.py:263: error: Unused "type: ignore" comment  [unused-ignore]
+ src/trio/_tests/test_util.py:264: error: Unused "type: ignore" comment  [unused-ignore]
+ src/trio/_tests/test_exports.py:398: error: "str" has no attribute "get"  [attr-defined]
+ src/trio/_tests/test_exports.py:400: error: Invalid index type "str" for "str"; expected type "Union[SupportsIndex, slice[Any, Any, Any]]"  [index]
+ src/trio/_tests/test_exports.py:403: error: "str" has no attribute "get"  [attr-defined]

pwndbg (https://github.com/pwndbg/pwndbg)
+ pwndbg/enhance.py:185: error: Unused "type: ignore" comment  [unused-ignore]
+ pwndbg/enhance.py:187: error: Unused "type: ignore" comment  [unused-ignore]

ibis (https://github.com/ibis-project/ibis)
+ ibis/formats/polars.py:143: error: "Sequence[Any]" has no attribute "dtype"  [attr-defined]
- ibis/expr/types/relations.py:3994: error: "list[Any]" has no attribute "unnest"  [attr-defined]
+ ibis/expr/types/joins.py:355: error: Argument "predicates" to "JoinLink" has incompatible type "list[Any]"; expected "tuple[Value[Boolean, Any], ...]"  [arg-type]
- ibis/expr/types/temporal_windows.py:68: error: Argument 1 to "unwrap_aliases" has incompatible type "str | Value | Sequence[str] | Sequence[Value] | None"; expected "Iterator[Value]"  [arg-type]
+ ibis/expr/types/temporal_windows.py:68: error: Argument 1 to "unwrap_aliases" has incompatible type "Any | Value | Sequence[str] | Sequence[Value]"; expected "Iterator[Value]"  [arg-type]
- ibis/expr/types/temporal_windows.py:69: error: Argument 1 to "unwrap_aliases" has incompatible type "Sequence[Scalar] | None"; expected "Iterator[Value]"  [arg-type]
+ ibis/expr/types/temporal_windows.py:69: error: Argument 1 to "unwrap_aliases" has incompatible type "Sequence[Scalar] | Any"; expected "Iterator[Value]"  [arg-type]
- ibis/expr/types/temporal_windows.py:72: error: Argument 1 to "update" of "MutableMapping" has incompatible type "str | Value | Sequence[str] | Sequence[Value] | None"; expected "Iterable[tuple[Any, Any]]"  [arg-type]
+ ibis/expr/types/temporal_windows.py:72: error: Argument 1 to "update" of "MutableMapping" has incompatible type "Any | Value | Sequence[str] | Sequence[Value]"; expected "SupportsKeysAndGetItem[Any, Any]"  [arg-type]
- ibis/expr/types/temporal_windows.py:79: error: Argument "metrics" to "WindowAggregate" has incompatible type "Sequence[ibis.expr.types.generic.Scalar] | None"; expected "FrozenOrderedDict[str, Value[Any, ibis.expr.datashape.Scalar]]"  [arg-type]
+ ibis/expr/types/temporal_windows.py:79: error: Argument "metrics" to "WindowAggregate" has incompatible type "Sequence[ibis.expr.types.generic.Scalar] | Any"; expected "FrozenOrderedDict[str, Value[Any, ibis.expr.datashape.Scalar]]"  [arg-type]
- ibis/backends/sql/compilers/trino.py:638: error: "int" has no attribute "as_"  [attr-defined]
+ ibis/backends/sql/compilers/trino.py:638: error: Item "int" of "int | Any" has no attribute "as_"  [union-attr]
- ibis/backends/sql/compilers/trino.py:638: error: Unsupported operand types for - ("None" and "int")  [operator]
- ibis/backends/sql/compilers/trino.py:638: note: Left operand is of type "str | None"
+ ibis/backends/sql/compilers/trino.py:638: note: Left operand is of type "str | Any"
- ibis/backends/sql/compilers/postgres.py:783: error: "int" has no attribute "as_"  [attr-defined]
+ ibis/backends/sql/compilers/postgres.py:783: error: Item "int" of "int | Any" has no attribute "as_"  [union-attr]
- ibis/backends/sql/compilers/postgres.py:783: error: Unsupported operand types for - ("None" and "int")  [operator]
- ibis/backends/sql/compilers/postgres.py:783: note: Left operand is of type "str | None"
+ ibis/backends/sql/compilers/postgres.py:783: note: Left operand is of type "str | Any"
+ ibis/tests/benchmarks/benchfuncs.py:112: error: Unsupported operand types for & ("BooleanValue" and "bool")  [operator]
- ibis/tests/benchmarks/benchfuncs.py:296: error: Unsupported operand types for & ("BooleanValue" and "Value")  [operator]
- ibis/tests/benchmarks/benchfuncs.py:314: error: Incompatible return value type (got "tuple[Any, Any, Value]", expected "tuple[StringColumn, StringColumn, StringColumn]")  [return-value]
- ibis/backends/mssql/__init__.py:711: error: Item "Mapping[str, str | DataType]" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
+ ibis/backends/mssql/__init__.py:711: error: Item "Mapping[str, str | DataType]" of "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/mssql/__init__.py:711: error: Item "Iterable[tuple[str, str | DataType]]" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
+ ibis/backends/mssql/__init__.py:711: error: Item "Iterable[tuple[str, str | DataType]]" of "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/mssql/__init__.py:711: error: Item "None" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/mssql/__init__.py:758: error: Argument "schema" to "DatabaseTable" has incompatible type "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]"; expected "Schema"  [arg-type]
+ ibis/backends/mssql/__init__.py:758: error: Argument "schema" to "DatabaseTable" has incompatible type "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]"; expected "Schema"  [arg-type]
+ ibis/backends/sqlite/__init__.py:497: error: Item "Expr" of "Any | Expr | None" has no attribute "schema"  [union-attr]
- ibis/backends/sqlite/__init__.py:497: error: Item "None" of "Any | Any | Any | Any | Any | None" has no attribute "schema"  [union-attr]
+ ibis/backends/sqlite/__init__.py:497: error: Item "None" of "Any | Expr | None" has no attribute "schema"  [union-attr]
- ibis/backends/risingwave/__init__.py:223: error: Item "tuple[str, ...]" of "tuple[str, str] | str" has no attribute "args"  [union-attr]
- ibis/backends/risingwave/__init__.py:223: error: Item "str" of "tuple[str, str] | str" has no attribute "args"  [union-attr]
- ibis/backends/risingwave/__init__.py:227: error: Item "tuple[str, ...]" of "tuple[str, str] | str" has no attribute "args"  [union-attr]
- ibis/backends/risingwave/__init__.py:227: error: Item "str" of "tuple[str, str] | str" has no attribute "args"  [union-attr]
- ibis/backends/risingwave/__init__.py:600: error: Item "Mapping[str, str | DataType]" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
+ ibis/backends/risingwave/__init__.py:600: error: Item "Mapping[str, str | DataType]" of "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/risingwave/__init__.py:600: error: Item "Iterable[tuple[str, str | DataType]]" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
+ ibis/backends/risingwave/__init__.py:600: error: Item "Iterable[tuple[str, str | DataType]]" of "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/risingwave/__init__.py:600: error: Item "None" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/risingwave/__init__.py:640: error: Argument "schema" to "DatabaseTable" has incompatible type "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]"; expected "Schema"  [arg-type]
+ ibis/backends/risingwave/__init__.py:640: error: Argument "schema" to "DatabaseTable" has incompatible type "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]"; expected "Schema"  [arg-type]
- ibis/backends/postgres/__init__.py:331: error: Item "tuple[str, ...]" of "tuple[str, str] | str" has no attribute "args"  [union-attr]
- ibis/backends/postgres/__init__.py:331: error: Item "str" of "tuple[str, str] | str" has no attribute "args"  [union-attr]
- ibis/backends/postgres/__init__.py:335: error: Item "tuple[str, ...]" of "tuple[str, str] | str" has no attribute "args"  [union-attr]
- ibis/backends/postgres/__init__.py:335: error: Item "str" of "tuple[str, str] | str" has no attribute "args"  [union-attr]
- ibis/backends/postgres/__init__.py:659: error: Item "Mapping[str, str | DataType]" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
+ ibis/backends/postgres/__init__.py:659: error: Item "Mapping[str, str | DataType]" of "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/postgres/__init__.py:659: error: Item "Iterable[tuple[str, str | DataType]]" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
+ ibis/backends/postgres/__init__.py:659: error: Item "Iterable[tuple[str, str | DataType]]" of "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/postgres/__init__.py:659: error: Item "None" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/postgres/__init__.py:689: error: Argument "schema" to "DatabaseTable" has incompatible type "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]"; expected "Schema"  [arg-type]
+ ibis/backends/postgres/__init__.py:689: error: Argument "schema" to "DatabaseTable" has incompatible type "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]"; expected "Schema"  [arg-type]
- ibis/backends/oracle/__init__.py:310: error: Item "tuple[str, ...]" of "tuple[str, str] | str" has no attribute "sql"  [union-attr]
- ibis/backends/oracle/__init__.py:310: error: Item "str" of "tuple[str, str] | str" has no attribute "sql"  [union-attr]
- ibis/backends/oracle/__init__.py:606: error: Argument 1 to "Schema" has incompatible type "dict[str, DataType]"; expected "FrozenOrderedDict[str, DataType]"  [arg-type]
+ ibis/backends/oracle/__init__.py:606: error: Argument 1 to "Schema" has incompatible type "dict[Any, DataType]"; expected "FrozenOrderedDict[str, DataType]"  [arg-type]
- ibis/backends/mysql/__init__.py:434: error: Item "Mapping[str, str | DataType]" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
+ ibis/backends/mysql/__init__.py:434: error: Item "Mapping[str, str | DataType]" of "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/mysql/__init__.py:434: error: Item "Iterable[tuple[str, str | DataType]]" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
+ ibis/backends/mysql/__init__.py:434: error: Item "Iterable[tuple[str, str | DataType]]" of "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/mysql/__init__.py:434: error: Item "None" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/mysql/__init__.py:461: error: Argument "schema" to "DatabaseTable" has incompatible type "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]"; expected "Schema"  [arg-type]
+ ibis/backends/mysql/__init__.py:461: error: Argument "schema" to "DatabaseTable" has incompatible type "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]"; expected "Schema"  [arg-type]
- ibis/backends/exasol/__init__.py:391: error: Item "Mapping[str, str | DataType]" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
+ ibis/backends/exasol/__init__.py:391: error: Item "Mapping[str, str | DataType]" of "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/exasol/__init__.py:391: error: Item "Iterable[tuple[str, str | DataType]]" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
+ ibis/backends/exasol/__init__.py:391: error: Item "Iterable[tuple[str, str | DataType]]" of "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/exasol/__init__.py:391: error: Item "None" of "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]] | None" has no attribute "to_sqlglot"  [union-attr]
- ibis/backends/exasol/__init__.py:416: error: Argument "schema" to "DatabaseTable" has incompatible type "Schema | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]"; expected "Schema"  [arg-type]
+ ibis/backends/exasol/__init__.py:416: error: Argument "schema" to "DatabaseTable" has incompatible type "Schema | Any | Mapping[str, str | DataType] | Iterable[tuple[str, str | DataType]]"; expected "Schema"  [arg-type]
- ibis/backends/datafusion/__init__.py:381: error: Item "str" of "str | None" has no attribute "database"  [union-attr]
+ ibis/backends/datafusion/__init__.py:381: error: Item "str" of "str | Any" has no attribute "database"  [union-attr]
- ibis/backends/datafusion/__init__.py:381: error: Item "None" of "str | None" has no attribute "database"  [union-attr]
- ibis/backends/datafusion/__init__.py:383: error: Item "str" of "str | None" has no attribute "database"  [union-attr]
+ ibis/backends/datafusion/__init__.py:383: error: Item "str" of "str | Any" has no attribute "database"  [union-attr]
- ibis/backends/datafusion/__init__.py:383: error: Item "None" of "str | None" has no attribute "database"  [union-attr]
- ibis/backends/datafusion/__init__.py:385: error: Item "str" of "str | None" has no attribute "names"  [union-attr]
+ ibis/backends/datafusion/__init__.py:385: error: Item "str" of "str | Any" has no attribute "names"  [union-attr]
- ibis/backends/datafusion/__init__.py:385: error: Item "None" of "str | None" has no attribute "names"  [union-attr]
- ibis/backends/datafusion/__init__.py:388: error: Item "str" of "str | None" has no attribute "table"  [union-attr]
+ ibis/backends/datafusion/__init__.py:388: error: Item "str" of "str | Any" has no attribute "table"  [union-attr]
- ibis/backends/datafusion/__init__.py:388: error: Item "None" of "str | None" has no attribute "table"  [union-attr]
+ ibis/backends/clickhouse/__init__.py:698: error: Item "None" of "Any | Expr | None" has no attribute "schema"  [union-attr]
+ ibis/backends/clickhouse/__init__.py:698: error: Item "Expr" of "Any | Expr | None" has no attribute "schema"  [union-attr]

tornado (https://github.com/tornadoweb/tornado)
+ tornado/locale.py:445: error: Incompatible return value type (got "str", expected "bool")  [return-value]
+ tornado/locale.py:451: error: Incompatible return value type (got "str", expected "bool")  [return-value]

PyGithub (https://github.com/PyGithub/PyGithub)
+ github/GithubRetry.py:119: error: Unused "type: ignore" comment  [unused-ignore]

steam.py (https://github.com/Gobot1234/steam.py)
+ steam/app.py:1689: error: Unused "type: ignore" comment  [unused-ignore]
+ steam/clan.py:121: error: Item "str" of "Any | str" has no attribute "parts"  [union-attr]
+ steam/client.py:707: error: Item "str" of "Any | str" has no attribute "parts"  [union-attr]

pandera (https://github.com/pandera-dev/pandera)
+ pandera/backends/polars/container.py:270: error: Unused "type: ignore" comment  [unused-ignore]
+ pandera/backends/polars/container.py:277: error: Unused "type: ignore" comment  [unused-ignore]
+ pandera/backends/polars/container.py:281: error: Unused "type: ignore" comment  [unused-ignore]

jax (https://github.com/google/jax)
+ jax/_src/dtypes.py:151: error: Unused "type: ignore" comment  [unused-ignore]
+ jax/_src/dtypes.py:157: error: Unused "type: ignore" comment  [unused-ignore]
+ jax/_src/dtypes.py:163: error: Unused "type: ignore" comment  [unused-ignore]
+ jax/_src/lax/lax.py:4118: error: Argument 2 to "dot_algorithm_attr" has incompatible type "str | type[Any] | dtype[Any] | SupportsDType | None"; expected "str | type[Any] | dtype[Any] | SupportsDType"  [arg-type]
+ jax/_src/lax/lax.py:4118: error: Argument 3 to "dot_algorithm_attr" has incompatible type "str | type[Any] | dtype[Any] | SupportsDType | None"; expected "str | type[Any] | dtype[Any] | SupportsDType"  [arg-type]
+ jax/_src/cudnn/fused_attention_stablehlo.py:1704: error: Unused "type: ignore" comment  [unused-ignore]
+ jax/_src/image/scale.py:267: error: No overload variant of "__setitem__" of "list" matches argument types "int", "Array"  [call-overload]
+ jax/_src/image/scale.py:267: note: Possible overload variants:
+ jax/_src/image/scale.py:267: note:     def __setitem__(self, SupportsIndex, slice[int | Any, int | Any, int | Any], /) -> None
+ jax/_src/image/scale.py:267: note:     def __setitem__(self, slice[Any, Any, Any], Iterable[slice[int | Any, int | Any, int | Any]], /) -> None

discord.py (https://github.com/Rapptz/discord.py)
- discord/app_commands/tree.py:797: error: Argument 1 to "__delitem__" of "dict" has incompatible type "tuple[str, int | None, int]"; expected "str"  [arg-type]
- discord/app_commands/tree.py:806: error: Argument 1 to "__delitem__" of "dict" has incompatible type "tuple[str, int | None, int]"; expected "str"  [arg-type]
- discord/ui/select.py:181: error: Invalid index type "type[Snowflake]" for "dict[type[SelectDefaultValue] | type[Object] | type[Role] | type[Member] | type[ClientUser] | type[User] | type[GuildChannel] | type[AppCommandChannel] | type[AppCommandThread] | type[Thread], SelectDefaultValueType]"; expected type "type[SelectDefaultValue] | type[Object] | type[Role] | type[Member] | type[ClientUser] | type[User] | type[GuildChannel] | type[AppCommandChannel] | type[AppCommandThread] | type[Thread]"  [index]
- discord/ext/commands/help.py:978: error: Argument 2 has incompatible type "Command[None, [VarArg(Any), KwArg(Any)], Any] | None"; expected "Command[Any, [VarArg(Any), KwArg(Any)], Any]"  [arg-type]
- discord/ext/commands/help.py:982: error: Argument 2 has incompatible type "Command[None, [VarArg(Any), KwArg(Any)], Any] | None"; expected "Command[Any, [VarArg(Any), KwArg(Any)], Any]"  [arg-type]
- discord/ext/commands/help.py:989: error: Argument 1 to "send_command_help" of "HelpCommand" has incompatible type "Command[None, [VarArg(Any), KwArg(Any)], Any] | None"; expected "Command[Any, [VarArg(Any), KwArg(Any)], Any]"  [arg-type]

aiohttp (https://github.com/aio-libs/aiohttp)
+ aiohttp/formdata.py:93: error: Unused "type: ignore" comment  [unused-ignore]

core (https://github.com/home-assistant/core)
- homeassistant/data_entry_flow.py:361: error: "object" not callable  [operator]
- homeassistant/helpers/template.py:185: error: Returning Any from function declared to return "TemplateState"  [no-any-return]
- homeassistant/helpers/template.py:194: error: Returning Any from function declared to return "TemplateState"  [no-any-return]
+ homeassistant/components/system_health/__init__.py:195: error: Incompatible types in assignment (expression has type "bool", target has type "str")  [assignment]
+ homeassistant/components/system_health/__init__.py:196: error: Incompatible types in assignment (expression has type "dict[str, str]", target has type "str")  [assignment]
+ homeassistant/components/system_health/__init__.py:198: error: Incompatible types in assignment (expression has type "bool", target has type "str")  [assignment]
+ homeassistant/components/light/__init__.py:534: error: Too many arguments for "color_rgb_to_rgbww"  [call-arg]
+ homeassistant/components/light/__init__.py:604: error: Too many arguments for "color_rgbww_to_rgb"  [call-arg]
+ homeassistant/components/matter/binary_sensor.py:58: error: Incompatible types in assignment (expression has type "Any | int | None", variable has type "bool | None")  [assignment]
+ homeassistant/components/alexa/handlers.py:1612: error: Incompatible types in assignment (expression has type "float", variable has type "int | None")  [assignment]
+ homeassistant/components/alexa/handlers.py:1623: error: Incompatible types in assignment (expression has type "float", variable has type "int | None")  [assignment]
+ homeassistant/components/solarlog/coordinator.py:176: error: Returning Any from function declared to return "bool"  [no-any-return]
+ homeassistant/components/minecraft_server/api.py:88: error: Item "None" of "Any | None" has no attribute "timeout"  [union-attr]
- homeassistant/components/linear_garage_door/coordinator.py:56: error: Item "None" of "list[dict[str, Any]] | None" has no attribute "__iter__" (not iterable)  [union-attr]
- homeassistant/components/file_upload/__init__.py:187: error: Item "None" of "Future[None] | None" has no attribute "done"  [union-attr]
- homeassistant/components/yalexs_ble/config_flow.py:131: error: Argument 2 to "async_find_existing_service_info" has incompatible type "str | None"; expected "str"  [arg-type]
- homeassistant/components/reolink/host.py:188: error: Argument 1 to "async_save" of "Store" has incompatible type "str | None"; expected "str"  [arg-type]
+ homeassistant/components/recorder/history/modern.py:769: error: Unused "type: ignore" comment  [unused-ignore]
- homeassistant/components/hyperion/config_flow.py:338: error: Dict entry 0 has incompatible type "str": "str | None"; expected "str": "str"  [dict-item]
+ homeassistant/components/frontier_silicon/media_player.py:247: error: Value of type variable "SupportsRichComparisonT" of "min" cannot be "int | None"  [type-var]
- homeassistant/components/deconz/entity.py:93: error: Unsupported left operand type for + ("None")  [operator]
- homeassistant/components/deconz/entity.py:93: note: Left operand is of type "str | None"
+ homeassistant/components/androidtv/config_flow.py:390: error: Unused "type: ignore" comment  [unused-ignore]
- homeassistant/components/switchbee/climate.py:173: error: Dict entry 0 has incompatible type "Any": "str | None"; expected "str": "int | str"  [dict-item]
+ homeassistant/components/sonos/alarms.py:69: error: Unsupported operand types for <= ("int" and "None")  [operator]
+ homeassistant/components/sonos/alarms.py:69: note: Right operand is of type "int | None"
- homeassistant/components/homekit_controller/config_flow.py:139: error: Argument 1 to "normalize_hkid" has incompatible type "str | None"; expected "str"  [arg-type]
+ homeassistant/components/zha/radio_manager.py:423: error: Unused "type: ignore" comment  [unused-ignore]
+ homeassistant/components/tplink/config_flow.py:334: error: Incompatible types in assignment (expression has type "int", target has type "str")  [assignment]
+ homeassistant/components/isy994/sensor.py:204: error: Argument 1 to "temperature" of "UnitSystem" has incompatible type "float | int | None"; expected "float"  [arg-type]
- homeassistant/components/knx/config_flow.py:201: error: Argument 1 to "anext" has incompatible type "AsyncGenerator[Any, None] | None"; expected "_SupportsSynchronousAnext[Coroutine[Any, Any, Any]]"  [arg-type]
+ homeassistant/components/fully_kiosk/sensor.py:155: error: Argument 1 to "round_storage" has incompatible type "str | int | float | Any | None"; expected "int"  [arg-type]

python-chess (https://github.com/niklasf/python-chess)
+ chess/pgn.py:1315: error: Returning Any from function declared to return "HeadersT"  [no-any-return]

ignite (https://github.com/pytorch/ignite)
+ ignite/distributed/comp_models/base.py:271: error: Item "float" of "Any | float | str" has no attribute "item"  [union-attr]
+ ignite/distributed/comp_models/base.py:271: error: Item "str" of "Any | float | str" has no attribute "item"  [union-attr]
+ ignite/handlers/lr_finder.py:182: error: Incompatible types in assignment (expression has type "Any | float", variable has type "None")  [assignment]

mitmproxy (https://github.com/mitmproxy/mitmproxy)
+ mitmproxy/utils/debug.py:72: error: "Thread" has no attribute "_thread_started"  [attr-defined]

kornia (https://github.com/kornia/kornia)
+ kornia/feature/lightglue_onnx/lightglue.py:72: error: Item "str" of "str | Any" has no attribute "type"  [union-attr]
+ kornia/feature/lightglue_onnx/lightglue.py:74: error: Item "str" of "str | Any" has no attribute "type"  [union-attr]
+ kornia/feature/lightglue_onnx/lightglue.py:80: error: Item "str" of "str | Any" has no attribute "type"  [union-attr]
+ kornia/feature/lightglue_onnx/lightglue.py:84: error: Item "str" of "str | Any" has no attribute "type"  [union-attr]
+ kornia/feature/lightglue_onnx/lightglue.py:87: error: Item "str" of "str | Any" has no attribute "type"  [union-attr]
+ kornia/feature/lightglue_onnx/lightglue.py:138: error: Item "str" of "str | Any" has no attribute "type"  [union-attr]
+ kornia/feature/lightglue_onnx/lightglue.py:167: error: Item "str" of "str | Any" has no attribute "type"  [union-attr]
+ kornia/feature/lightglue_onnx/lightglue.py:175: error: Item "str" of "str | Any" has no attribute "type"  [union-attr]
+ kornia/enhance/adjust.py:726: error: Item "float" of "float | Any" has no attribute "__iter__" (not iterable)  [union-attr]
+ kornia/augmentation/_2d/mix/transplantation.py:182: error: Item "Sequence[int]" of "Sequence[int] | Any" has no attribute "ndim"  [union-attr]
+ kornia/augmentation/_2d/mix/transplantation.py:183: error: Item "Sequence[int]" of "Sequence[int] | Any" has no attribute "ndim"  [union-attr]

poetry (https://github.com/python-poetry/poetry)

... (truncated 28 lines) ...```

@cdce8p
Copy link
Collaborator

cdce8p commented Feb 1, 2025

I'd need to adjust the state type here to str | Any | None.

No, you need to change annotation on x to Any | None, so that you will have

def b1(x: Any | None) -> str:
    state: str | None = None
    state = x
    reveal_type(state)  # Any | None
    return state

This unfortunately doesn't work if the second assignment is a function call.

def b1() -> str:
    state: str | None = None
    state = untyped()
    reveal_type(state)  # str | None -> str | Any
    return state

I don't see a case where it's arguably an improvement to "overwrite" the explicit annotation

For example this:

def fallback(): return 42  # untyped

def use_fallback(x: int | None) -> int:
    if x is None:
        x = fallback()
    return x

without special casing this will result in a false-positive. And a false positive is usually more annoying than a false negative.

Is it a false-positive though? Your example assumes that fallback will always return a not-none value. What about this?

def fallback():
    if int():
        return 42
    return None

def use_fallback() -> int:
    x: int | None = None
    if x is None:
        x = fallback()
    return x

I'd argue by choosing an explicit annotation, the caller assumes the responsibility to make sure each assignment (with Any) still respects the intended type. This could e.g. be done with x = cast(int, fallback()) or assert x is not None. Replacing None with Any feels wrong and hides potential issues IMO.

Yes, false-positives are annoying, but it's even worse if mypy could detect potential issues but chooses not to just because of false-positives.

Note as I was testing your example, I noticed that if x is None: x = fallback() already replaces None with Any even on master. If we change something, I'd argue this should be adjust to keep the original type instead. I'd love to know were in the Home Assistant code base this hides potential errors which are currently unnoticed.

If false-positives are really an issue, this could even be a strict flag.

@ilevkivskyi
Copy link
Member Author

@cdce8p

Is it a false-positive though?

Yes, it is. I already explained to you that always narrowing to Any is a correct thing to do. And we are not doing this in some (actually in many) cases only from "practicality" reasons. Please stop arguing, in is not helpful.

@ilevkivskyi
Copy link
Member Author

Btw @JukkaL I think at this point it makes sense for you to review this and/or try this against Dropbox (or other) codebases.

@ilevkivskyi
Copy link
Member Author

@cdce8p Also couple educational things, because it seems to me you may lack some context here.

First: gradual typing is not about gradually adding types to an existing code base. As far as I know, the original motivation and all the "rules" about Any (which is a confusing name btw, it should have been Dynamic probably, but that is too long) come from the problem that types of some expressions are impossible or impractical to express (for example an intersection of a callable and an instance before we added extended callback protocols). Adding types to an existing code bases however is a (very) important use case. Therefore mypy often deviates from the "rules" to accommodate this use case as much as reasonable.

Second: when the variable annotations were first introduced (in PEP 526), Mark Shannon had an objection, that local variables (unlike global or class level ones that specify module/class interface) are very non-pythonic (or even anty-pythonic) thing. And I actually agree with him. There are no variables in Python, only names to refer to objects. But IIRC one of the key arguments to convince him that we need local type annotations was the lack of syntax for a function type application. Consider this:

def fn[T](x: T) -> list[T]: ...

class B: ...
class C(B): ...

def foo(x: list[B]): ...
c = C()

x = fn(c)
foo(x)  # Error because of invariance

y: list[B] = fn(c)
foo(y)  # OK

I would actually much rather be able to write y = fn[B](c) instead. To make sure we are specifically targeting this use, Mark asked me to add some wording to the PEP (don't remember it exactly) implying that the local variable annotation is a type hint for the current assignment only, not for the entire scope of the name. So this was always the ultimate intent.

@cdce8p
Copy link
Collaborator

cdce8p commented Feb 2, 2025

Thanks for the background info @ilevkivskyi. You're right that I lack some of the context that you guys have. I only became really interested in typing 3-4 years ago.

Please stop arguing, in is not helpful.

I agree here as well. I do understand your concern, so I wanted to test the actual impact of my suggestion. For my first test I removed the "second" and "third" special case for Any completely. The result was pretty clear with about 50 new errors for Home Assistant, most of which indeed being false-positives. While looking through them, I did notice a common pattern though

class X: ...  # defined in an untyped dependency

def f() -> None:
    var: X | None = None  # Any | None
    if var is None:
        var = untyped_return_x()
    reveal_type(var)

That got me thinking what would happen if I change the "second" case to only apply if the union contains both None and Any. With that the false-positives went down to just 2 while still covering at lot of the issues I mentioned earlier.

Would that be an option for you here?

@ilevkivskyi
Copy link
Member Author

@cdce8p

Would that be an option for you here?

No, we still had several bug reports even with full scale "second" case. For example #3526 and its duplicate #3431, this is why "third" case was added.

Moreover, we still have an open issue #3724 that would be fixed by this PR (because now "third" case is applied uniformly, instead of some ad-hoc way).

@JukkaL
Copy link
Collaborator

JukkaL commented Feb 3, 2025

I tried this with with the biggest Dropbox codebase we have, and this generated about 130 new errors. I spot checked a sample of them, and all looked like legitimate errors. So if mypy_primer looks good, the impact seems positive and not too disruptive (at least in terms of false positives).

@ilevkivskyi
Copy link
Member Author

I looked at mypy_primer again, and noticed couple new errors related to this:

def untyped(): ...

class A: ...
class C: ...

x = C()
x = untyped()
if not isinstance(x, C):
    if isinstance(x, A):
        reveal_type(x)  # previously whole top block was unreachable, now the type here is "Never"

FWIW there is no simple fix for this. I am going to merge now, and if people will complain, I can spend some more time on this.

@ilevkivskyi ilevkivskyi merged commit b50f3a1 into python:master Feb 5, 2025
18 checks passed
@ilevkivskyi ilevkivskyi deleted the binder-use-union branch February 5, 2025 00:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Assigning a variable to the result of an untyped method removes any existing type constraints
4 participants